#include "StdAfx.hpp"
#include "Engine.hpp"
#include "BaseObject.hpp"
#include "PhysxUtils.hpp"
#include <NxCooking.h>
#include <NxControllerManager.h>

using namespace common;


//HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH
// class ObjectIterator

ObjectIterator & ObjectIterator::operator ++ ()
{
	assert(m_Ptr);
	m_Ptr = m_Ptr->m_NextObject;
	return *this;
}


//HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH
// class Engine

Engine *g_Engine = NULL;

Engine::Engine(bool usePhysxCooking)
: m_FirstObject(NULL), m_LastObject(NULL)
, m_FirstUpdateObject(NULL), m_LastUpdateObject(NULL)
, m_NextObjectId(1)
, m_Physx(NULL)
, m_UsePhysxCooking(usePhysxCooking)
, m_PhysxControllerManager(NULL)
{
	CreatePhysics();
}

Engine::~Engine()
{
	while (m_FirstObject)
	{
		m_FirstObject->m_RefCount = 0;
		m_FirstObject->m_PrevObject = m_FirstObject->m_NextObject = NULL;
		delete m_FirstObject;
	}

	DestroyPhysics();
}

void Engine::CreatePhysics()
{
	m_Physx = NxCreatePhysicsSDK(NX_PHYSICS_SDK_VERSION, NULL, &g_NxLogger);
	if (m_Physx == NULL)
		throw Error("Cannot initialize PhysX", __FILE__, __LINE__);

    NxReal scale = 1.0f;   // scale is meters per PhysX units
	NxReal scaleInv = 1.f / scale;
    m_Physx->setParameter(NX_SKIN_WIDTH, 0.05f*scaleInv);
    m_Physx->setParameter(NX_DEFAULT_SLEEP_LIN_VEL_SQUARED, 0.15f*0.15f*scaleInv*scaleInv);
    m_Physx->setParameter(NX_BOUNCE_THRESHOLD, -2.f*scaleInv);
    m_Physx->setParameter(NX_VISUALIZATION_SCALE, 0.5f*scaleInv);

	if (m_UsePhysxCooking)
		NxInitCooking(NULL, &g_NxLogger);

	m_PhysxControllerManager = NxCreateControllerManager(&m_Physx->getFoundationSDK().getAllocator());
	assert(m_PhysxControllerManager);
}

void Engine::DestroyPhysics()
{
	if (m_PhysxControllerManager)
	{
		NxReleaseControllerManager(m_PhysxControllerManager);
		m_PhysxControllerManager = NULL;
	}

	if (m_UsePhysxCooking)
		NxCloseCooking();

	if (m_Physx)
	{
		m_Physx->release();
		m_Physx = NULL;
	}
}

void Engine::OnUpdate()
{
	BaseObject *UpdateObj = m_FirstUpdateObject, *NextUpdateObj;
	while (UpdateObj)
	{
		NextUpdateObj = UpdateObj->m_NextUpdateObject;
		UpdateObj->OnUpdate();
		UpdateObj = NextUpdateObj;
	}

	DeferredDelete();
}

ObjectIterator Engine::FindObjectById(uint Id)
{
	IdToObjectHashMap::iterator IdToObjectIt = m_IdToObjectMap.find(Id);
	if (IdToObjectIt == m_IdToObjectMap.end())
		return ObjectsEnd();
	else
		return ObjectToIterator(IdToObjectIt->second);
}

ObjectIterator Engine::FindObjectByName(const string &Name)
{
	NameToObjectMap::iterator NameToObjectIt = m_NameToObjectMap.find(Name);
	if (NameToObjectIt == m_NameToObjectMap.end())
		return ObjectsEnd();
	else
		return ObjectToIterator(NameToObjectIt->second);
}

ObjectIterator Engine::MustFindObjectById(uint Id)
{
	ObjectIterator it = FindObjectById(Id);
	if (it == ObjectsEnd())
		throw Error(Format("Engine could not find object with id #") % Id, __FILE__, __LINE__);
	return it;
}

ObjectIterator Engine::MustFindObjectByName(const string &Name)
{
	ObjectIterator it = FindObjectByName(Name);
	if (it == ObjectsEnd())
		throw Error(Format("Engine could not find object with name \"#\"") % Name, __FILE__, __LINE__);
	return it;
}

void Engine::RegisterObject(BaseObject *obj)
{
	assert(obj->m_PrevObject == NULL && obj->m_NextObject == NULL);

	obj->m_Id = m_NextObjectId++;

	if (GetObjectCount() == 0)
		m_FirstObject = m_LastObject = obj;
	else
	{
		obj->m_PrevObject = m_LastObject;
		m_LastObject->m_NextObject = obj;
		m_LastObject = obj;
	}

	m_IdToObjectMap.insert(IdToObjectHashMap::value_type(obj->m_Id, obj));
}

void Engine::UnregisterObject(BaseObject *obj)
{
	if (!obj->GetName().empty())
	{
		NameToObjectMap::iterator NameToObjectIt = m_NameToObjectMap.find(obj->GetName());
		assert(NameToObjectIt != m_NameToObjectMap.end());
		m_NameToObjectMap.erase(NameToObjectIt);
	}

	IdToObjectHashMap::iterator IdToObjectIt = m_IdToObjectMap.find(obj->GetId());
	assert(IdToObjectIt != m_IdToObjectMap.end());
	m_IdToObjectMap.erase(IdToObjectIt);

	if (obj->m_PrevObject == NULL)
		m_FirstObject = obj->m_NextObject;
	else
		obj->m_PrevObject->m_NextObject = obj->m_NextObject;
	
	if (obj->m_NextObject == NULL)
		m_LastObject = obj->m_PrevObject;
	else
		obj->m_NextObject->m_PrevObject = obj->m_PrevObject;

	obj->m_PrevObject = obj->m_NextObject = NULL;
}

void Engine::RegisterObjectName(BaseObject *obj)
{
	if (!obj->GetName().empty())
	{
		bool ok = m_NameToObjectMap.insert(NameToObjectMap::value_type(obj->GetName(), obj)).second;
		assert(ok && "Object with that name already exists");
	}
}

void Engine::AddObjectToUpdateList(BaseObject *obj)
{
	assert(obj->m_PrevUpdateObject == NULL && obj->m_NextUpdateObject == NULL);

	if (m_FirstUpdateObject == NULL)
		m_FirstUpdateObject = m_LastUpdateObject = obj;
	else
	{
		obj->m_PrevUpdateObject = m_LastUpdateObject;
		m_LastUpdateObject->m_NextUpdateObject = obj;
		m_LastUpdateObject = obj;
	}
}

void Engine::RemoveObjectFromUpdateList(BaseObject *obj)
{
	if (obj->m_PrevUpdateObject == NULL)
		m_FirstUpdateObject = obj->m_NextUpdateObject;
	else
		obj->m_PrevUpdateObject->m_NextUpdateObject = obj->m_NextUpdateObject;
	
	if (obj->m_NextUpdateObject == NULL)
		m_LastUpdateObject = obj->m_PrevUpdateObject;
	else
		obj->m_NextUpdateObject->m_PrevUpdateObject = obj->m_PrevUpdateObject;

	obj->m_PrevUpdateObject = obj->m_NextUpdateObject = NULL;
}

void Engine::DeferredDelete()
{
	for (size_t i = 0; i < m_ObjectsForDeferredDelete.size(); i++)
		delete m_ObjectsForDeferredDelete[i];
	m_ObjectsForDeferredDelete.clear();
}

void Engine::RegisterObjectForDeferredDelete(BaseObject *obj)
{
	m_ObjectsForDeferredDelete.push_back(obj);
}
